﻿#region Header
// Copyright 2007-2011, Jerónimo Milea
// 
// This file is part of NetJsWire.
// 
//     NetJsWire is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
// 
//     NetJsWire is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
// 
//     You should have received a copy of the GNU General Public License
//     along with NetJsWire.  If not, see <http://www.gnu.org/licenses/>.
#endregion

using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Forms;

namespace NetJsWire.Lite
{

    internal class Native
    {
        #region Msg enum

        public enum Msg
        {
            WM_CREATE = 0x0001,
            WM_SETFOCUS = 0x0007,
            WM_PAINT = 0X000F,
            WM_CANCELMODE = 0x001F,
            WM_SETCURSOR = 0x0020,
            WM_CONTEXTMENU = 0x007B,
            WM_NCHITTEST = 0x0084,
            WM_ACTIVATE = 0x0006,
            WM_SETTEXT = 0x000C,
            WM_SETREDRAW = 0x000B,
            WM_NCACTIVATE = 0x0086,
            WM_NCCREATE = 0x0081,
            WM_NCCALCSIZE = 0x0083,
            //
            // No client paint
            //
            WM_NCPAINT = 0x0085,
            //
            // AccessabilityObject
            //
            WM_GETOBJECT = 0x003D,
            //
            // Mouse input - Client area
            //
            WM_MOUSEFIRST = 0x0200,
            WM_MOUSEMOVE = 0x0200,
            WM_LBUTTONDOWN = 0x0201,
            WM_LBUTTONUP = 0x0202,
            WM_LBUTTONDBLCLK = 0x0203,
            WM_RBUTTONDOWN = 0x0204,
            WM_RBUTTONUP = 0x0205,
            WM_RBUTTONDBLCLK = 0x0206,
            WM_MBUTTONDOWN = 0x0207,
            WM_MBUTTONUP = 0x0208,
            WM_MBUTTONDBLCLK = 0x0209,
            WM_MOUSEWHEEL = 0x020A,
            WM_MOUSELAST = 0x020A,
            WM_NCMOUSEHOVER = 0x02A0,
            WM_MOUSEHOVER = 0x02A1,
            WM_NCMOUSELEAVE = 0x02A2,
            WM_MOUSELEAVE = 0x02A3,
            //
            // Mouse input - Non-client area
            //
            WM_NCMOUSEMOVE = 0x00A0,
            WM_NCLBUTTONDOWN = 0x00A1,
            WM_NCLBUTTONUP = 0x00A2,
            WM_NCLBUTTONDBLCLK = 0x00A3,
            WM_NCRBUTTONDOWN = 0x00A4,
            WM_NCRBUTTONUP = 0x00A5,
            WM_NCRBUTTONDBLCLK = 0x00A6,
            WM_NCMBUTTONDOWN = 0x00A7,
            WM_NCMBUTTONUP = 0x00A8,
            WM_NCMBUTTONDBLCLK = 0x00A9,
            //
            // Keyboard input
            //
            WM_KEYFIRST = 0x0100,
            WM_KEYDOWN = 0x0100,
            WM_KEYUP = 0x0101,
            WM_CHAR = 0x0102,
            WM_DEADCHAR = 0x0103,
            WM_SYSKEYDOWN = 0x0104,
            WM_SYSKEYUP = 0x0105,
            WM_SYS1CHAR = 0x0106,
            WM_SYSDEADCHAR = 0x0107,
            WM_KEYLAST = 0x0108,
            //
            // Scrolling
            //
            WM_HSCROLL = 0x0114,
            WM_VSCROLL = 0x0115,

            //
            // IME - International Text
            //
            WM_IME_SETCONTEXT = 0x0281,
            WM_IME_NOTIFY = 0x0282,
            WM_IME_CONTROL = 0x0283,
            WM_IME_COMPOSITIONFULL = 0x0284,
            WM_IME_SELECT = 0x0285,
            WM_IME_CHAR = 0x0286,
            WM_IME_REQUEST = 0x0288,
            WM_IME_KEYDOWN = 0x0290,
            WM_IME_KEYUP = 0x0291,

            // MWF Custom msgs
            //
            WM_MOUSE_ENTER = 0x0401,
        }

        #endregion

        #region Win32 Native Method Signatures

        /// <summary>
        ///   Values to pass to the GetDCEx method.
        /// </summary>
        [Flags]
        public enum DC_VALUES : uint
        {
            /// <summary>
            ///   DCX_WINDOW: Returns a DC that corresponds to the window rectangle rather 
            ///   than the client rectangle.
            /// </summary>
            Window = 0x00000001,
            /// <summary>
            ///   DCX_CACHE: Returns a DC from the cache, rather than the OWNDC or CLASSDC 
            ///   window. Essentially overrides CS_OWNDC and CS_CLASSDC.
            /// </summary>
            Cache = 0x00000002,
            /// <summary>
            ///   DCX_NORESETATTRS: Does not reset the attributes of this DC to the 
            ///   default attributes when this DC is released.
            /// </summary>
            NoResetAttrs = 0x00000004,
            /// <summary>
            ///   DCX_CLIPCHILDREN: Excludes the visible regions of all child windows 
            ///   below the window identified by hWnd.
            /// </summary>
            ClipChildren = 0x00000008,
            /// <summary>
            ///   DCX_CLIPSIBLINGS: Excludes the visible regions of all sibling windows 
            ///   above the window identified by hWnd.
            /// </summary>
            ClipSiblings = 0x00000010,
            /// <summary>
            ///   DCX_PARENTCLIP: Uses the visible region of the parent window. The 
            ///   parent's WS_CLIPCHILDREN and CS_PARENTDC style bits are ignored. The origin is 
            ///   set to the upper-left corner of the window identified by hWnd.
            /// </summary>
            ParentClip = 0x00000020,
            /// <summary>
            ///   DCX_EXCLUDERGN: The clipping region identified by hrgnClip is excluded 
            ///   from the visible region of the returned DC.
            /// </summary>
            ExcludeRgn = 0x00000040,
            /// <summary>
            ///   DCX_INTERSECTRGN: The clipping region identified by hrgnClip is 
            ///   intersected with the visible region of the returned DC.
            /// </summary>
            IntersectRgn = 0x00000080,
            /// <summary>
            ///   DCX_EXCLUDEUPDATE: Unknown...Undocumented
            /// </summary>
            ExcludeUpdate = 0x00000100,
            /// <summary>
            ///   DCX_INTERSECTUPDATE: Unknown...Undocumented
            /// </summary>
            IntersectUpdate = 0x00000200,
            /// <summary>
            ///   DCX_LOCKWINDOWUPDATE: Allows drawing even if there is a LockWindowUpdate 
            ///   call in effect that would otherwise exclude this window. Used for drawing during 
            ///   tracking.
            /// </summary>
            LockWindowUpdate = 0x00000400,
            /// <summary>
            ///   DCX_VALIDATE When specified with DCX_INTERSECTUPDATE, causes the DC to 
            ///   be completely validated. Using this function with both DCX_INTERSECTUPDATE and 
            ///   DCX_VALIDATE is identical to using the BeginPaint function.
            /// </summary>
            Validate = 0x00200000,
        }

        [DllImport( "user32.dll", CharSet = CharSet.Auto )]
        private static extern IntPtr DefWindowProc( IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam );

        [DllImport( "user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto )]
        private static extern IntPtr SendMessage_Win32( IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam );

        [DllImport( "user32.dll", CharSet = CharSet.Auto )]
        private static extern bool ScreenToClient( IntPtr hWnd, ref POINT pt );

        [DllImport( "user32.dll", EntryPoint = "SetParent", CharSet = CharSet.Auto )]
        private static extern IntPtr SetParent_Win32( IntPtr childHandle, IntPtr parentHandle );

        //[DllImport( "user32.dll", EntryPoint = "GetDCEx", CharSet = CharSet.Auto, ExactSpelling = true )]
        //public static extern IntPtr GetDCEx( IntPtr hWnd, IntPtr hrgnClip, int flags );

        [DllImport( "user32.dll", EntryPoint = "GetDCEx", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true, CallingConvention = CallingConvention.StdCall )]
        public static extern IntPtr GetDCEx( IntPtr hWnd, IntPtr hrgnClip, DC_VALUES flags );

        [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true ), SuppressUnmanagedCodeSecurity]
        public static extern IntPtr GetDC( IntPtr windowHandle );

        [DllImport( "gdi32.dll", EntryPoint = "CreateRectRgn", CharSet = CharSet.Auto, ExactSpelling = true )]
        public static extern IntPtr CreateRectRgn( int x1, int y1, int x2, int y2 );

        [DllImport( "gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true )]
        public static extern int CombineRgn( IntPtr hRgn, IntPtr hRgn1, IntPtr hRgn2, int nCombineMode );

        [DllImport( "gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true )]
        public static extern bool DeleteObject( IntPtr hObject );

        [DllImport( "user32.dll", EntryPoint = "ReleaseDC", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true )]
        public static extern int ReleaseDC( IntPtr hWnd, IntPtr hDc );

        [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
        public static extern bool GetWindowRect( IntPtr hWnd, [In, Out] ref RECT rect );

        [DllImport( "user32.dll" )]
        public static extern IntPtr GetWindowDC( IntPtr hWnd );

        [DllImport( "uxtheme.dll", CharSet = CharSet.Auto )]
        public static extern int SetWindowTheme( IntPtr hWnd, string subAppName, string subIdList );

        #region Nested type: NCCALCSIZE_PARAMS

        [StructLayout( LayoutKind.Sequential )]
        public struct NCCALCSIZE_PARAMS
        {
            public RECT[] rgrc;
            public WINDOWPOS lppos;

            public RECT rectProposed
            {
                get { return rgrc[ 0 ]; }
                set { rgrc[ 0 ] = value; }
            }
        }

        #endregion

        #region Nested type: POINT

        [StructLayout( LayoutKind.Sequential )]
        private struct POINT
        {
            public POINT( int x, int y )
            {
                X = x;
                Y = y;
            }

            public readonly int X;
            public readonly int Y;
        }

        #endregion

        #region Nested type: RECT

        [StructLayout( LayoutKind.Sequential )]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public RECT( int left, int top, int right, int bottom )
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            public RECT( Rectangle r )
            {
                left = r.Left;
                top = r.Top;
                right = r.Right;
                bottom = r.Bottom;
            }

            public static RECT FromXYWH( int x, int y, int width, int height )
            {
                return new RECT( x, y, x + width, y + height );
            }

            public Size Size
            {
                get
                {
                    return new Size( right - left, bottom - top );
                }
            }
            public Rectangle ToRectangle()
            {
                return new Rectangle( left, top, right - left, bottom - top );
            }

            internal static RECT FromRectangle( Rectangle proposed )
            {
                return new RECT( proposed );
            }
        }

        #endregion

        #region Nested type: SHORTPOINT

        [StructLayout( LayoutKind.Sequential )]
        public struct SHORTPOINT
        {
            public SHORTPOINT( short x, short y )
            {
                X = x;
                Y = y;
            }

            public short X;
            public short Y;
        }

        #endregion

        #region Nested type: WINDOWPOS

        [StructLayout( LayoutKind.Sequential )]
        public struct WINDOWPOS
        {
            readonly IntPtr hwnd;
            readonly IntPtr hwndInsertAfter;
            readonly int x;
            readonly int y;
            readonly int cx;
            readonly int cy;
            readonly uint flags;
        }

        #endregion

        #endregion

        private static readonly Type _xplatuiType;
        private static readonly bool _onMSNET;

        static Native()
        {
            _onMSNET = false;
            var assembly = Assembly.Load( "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" );
            if ( assembly == null )
                throw new InvalidOperationException( "Can't load System.Windows.Forms assembly." );

            _xplatuiType = assembly.GetType( "System.Windows.Forms.XplatUI" );
            if ( _xplatuiType == null )
                _onMSNET = true;
        }

        private static object InvokeMethod( string methodName, object[] args )
        {
            return InvokeMethod( methodName, args, null );
        }

        // will also match types
        private static object InvokeMethod( string methodName, object[] args, Type[] types )
        {
            MethodInfo method = null;

            if ( types != null )
            {
                method = _xplatuiType.GetMethod( methodName, BindingFlags.NonPublic | BindingFlags.Static |
                                                                                                                BindingFlags.InvokeMethod, null, types, null );
            }
            else
            {
                method = _xplatuiType.GetMethod( methodName, BindingFlags.NonPublic | BindingFlags.Static |
                                                                                 BindingFlags.InvokeMethod );
            }

            if ( method == null )
                throw new InvalidOperationException( methodName + " not found!" );

            return method.Invoke( null, args );
        }

        public static void DefWndProc( ref Message m )
        {
            if ( _onMSNET )
            {
                m.Result = DefWindowProc( m.HWnd, m.Msg, m.WParam, m.LParam );
            }
            else
            {
                var args = new object[] { m };
                m.Result = ( IntPtr ) InvokeMethod( "DefWndProc", args );
                m = ( Message ) args[ 0 ];
            }
        }

        public static IntPtr SendMessage( IntPtr hwnd, Msg message, IntPtr wParam, IntPtr lParam )
        {
            if ( _onMSNET )
            {
                return SendMessage_Win32( hwnd, ( int ) message, wParam, lParam );
            }
            else
            {
                var assembly = Assembly.Load( "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" );
                var refType = assembly.GetType( "System.Windows.Forms.Message" ).MakeByRefType();
                var args = new object[] { Message.Create( hwnd, ( int ) message, wParam, lParam ) };
                InvokeMethod( "SendMessage", args, new[] { refType } );
                return ( ( Message ) args[ 0 ] ).Result;
            }
        }

        public static Point PointToClient( Control control, Point point )
        {
            if ( control == null )
                throw new ArgumentNullException( "control" );

            if ( _onMSNET )
            {
                var pt = new POINT( point.X, point.Y );
                ScreenToClient( control.Handle, ref pt );
                return new Point( pt.X, pt.Y );
            }
            else
            {
                var args = new object[] { control.Handle, point.X, point.Y };
                InvokeMethod( "ScreenToClient", args );
                return new Point( ( int ) args[ 1 ], ( int ) args[ 2 ] );
            }
        }

        public static IntPtr SetParent( IntPtr childHandle, IntPtr parentHandle )
        {
            if ( _onMSNET )
                return SetParent_Win32( childHandle, parentHandle );
            else
                return ( IntPtr ) InvokeMethod( "SetParent", new object[] { childHandle, parentHandle } );
        }

        #region Helpers
        public static int HiWord( int dword )
        {
            // 12345678 -> 12340000 -> 00001234
            return ( ( dword >> 16 ) & 0x0000ffff );
        }

        public static int LoWord( int dword )
        {
            // 12345678 -> 00005678
            return ( dword & 0x0000ffff );
        }

        public static IntPtr LParam( int hiword, int loword )
        {
            // results [hiword|loword] dword
            //
            return ( IntPtr ) ( ( loword << 16 ) | ( hiword & 0x0000FFFF ) );
        }
        #endregion
    }
}